const Cc = Components.classes;
const Ci = Components.interfaces;

const kXMLHTTP_CID = "@mozilla.org/xmlextras/xmlhttprequest;1";
const nsIXmlHttpRequest = Ci.nsIXMLHttpRequest;

const kHTTP_POLLING_CID = "@mozilla.org/httppolling;1";
const kHTTP_POLLING_UID = Components.ID("8abf9999-7546-4add-83a3-7dbd30df5544");

const nsIHttpPolling = Ci.nsIHttpPolling;
const nsISupports = Ci.nsISupports;

const kTIMER_CID = "@mozilla.org/timer;1";
const kXMLDOC_CID = "@mozilla.org/xul/xul-document;1";
const nsIDOMDocumentEvent = Ci.nsIDOMDocumentEvent;
const kXMLSerializer = "@mozilla.org/xmlextras/xmlserializer;1";
const nsIDOMserializer = Ci.nsIDOMSerializer;

var dom = Cc[kXMLDOC_CID].getService(nsIDOMDocumentEvent);
var parser = Cc["@mozilla.org/xmlextras/domparser;1"].getService(Ci.nsIDOMParser);
const _DEBUG = true;
const _HAVE_KEYS = true;
const _NKEYS = 256;

function nsHttpPolling ()
{
  this.url = null;
  this.user = null;
  this.pass = null;
  this.domain = null;
  this.sid = null; 
  var responseText = null;
  var responseXml = null;
  this.keys = new JSJaCKeys();
  this.connection=null;
  this.listeners=new Array();
  this.prefBranch = null;
  this.count = 0;
  this.initKeys();
}


nsHttpPolling.prototype.setDomain = function (domain){this.domain = domain;}
nsHttpPolling.prototype.setUrl = function (url){this.url = url;}
nsHttpPolling.prototype.setUser = function (user){this.user = user;}
nsHttpPolling.prototype.setPass = function (pass){this.pass = pass;}

nsHttpPolling.prototype.getListenersByType= function(type){
  if (this.listeners[type] == null ) {
	this.listeners[type] = new Array();
  }
  return this.listeners[type];
}

nsHttpPolling.prototype.addEventListener = function(type, aListener, capture)
{
  var lsr = this.getListenersByType(type);
  var run = 0;
  var found = false;
  while (!found && (run < lsr.length)){
    if (lsr[run] == aListener) {
      found = true;
	  break;
    }
    run++;  
  }
  if(!found)
		lsr.push(aListener);
}

nsHttpPolling.prototype.removeEventListener = function(type, aListener, capture) {
  var run = 0;
  var lsr = this.getListenersByType(type);
  
  if(lsr== null)
    return;
    
  while (run < lsr.length) {
    if (lsr[run] == aListener) {
	  lsr.removeElementAt(run);
	  break;
    }
    run++;  
  }
}

nsHttpPolling.prototype.handleEvent = function onConnectionEvent(evt) {
   dumpString("Count: " + this.count);
   //this.connection.removeEventListener("load", this, false);	
   switch(this.connection.readyState)
   {
     case 0:
       dumpString("Connection intialized");
     break;
     case 1:
       dumpString("Connection open");
     break;
     case 2:
       dumpString("Data sent");
     break;
     case 3:
       dumpString("Receiving data");
     break;
     case 4:
		 this.count ++;
		 this.responseText = this.connection.responseText;
		 this.responseXml = this.connection.responseXml || 
							parser.parseFromString("<response>"+this.responseText+"</response>", "text/xml");
		 dumpString("Received text: " + (this.connection.responseXml || this.responseText));
		 this.handleResponse();
		 if (this.count < 3 && this.connection.responseText=="")
		 {
			 this.sendRequest("");
		 }
		 this.count --;
    break;
   }	
}

nsHttpPolling.prototype.extractSid = function ()
{
	var aPList = this.connection.getResponseHeader('Set-Cookie');
	aPList = aPList.split(";");
	for (var i=0;i<aPList.length;i++) {
	   var aArg = aPList[i].split("=");
	   if (aArg[0] == 'ID')
		 this.sid = aArg[1];
	}
}

nsHttpPolling.prototype.handleResponse = function()
{
	var error = "";
	type = "error";
	
	try
	{
		this.extractSid();
	}		
	catch (e) //hmmmm, bad header proxy error ?
	{
		this.keys.indexAt ++;
		return;
	}

	switch (this.sid)
	{
		case "0:0":
			error="unknown error";
		break;
		case "-1:0":
			error ="server error";
		break;
		case "-2:0":
			error="bad request";
		break;
		case "-3:0":
			error="bad key sequence";
			this.sid="0";
			this.keys = new JSJaCKeys();
		break;
		default:
			type = "load";
		break;
	}

	if(error != "")
		dumpString("Error: " + error);
	
	this.dispatchEvent(this.getListenersByType(type));
}

function Dispatcher(handler){
	this.hdl = handler;
}

Dispatcher.prototype.run = function(){
	if (this.hdl){ 
			this.hdl.handleEvent(null);
	}
	else {
		dumpString("handler");
	}
}

nsHttpPolling.prototype.dispatchEvent = function (lsr)
{
    if (!lsr || !lsr.length) {
		dumpString("no listeners");
		return;
	}
	for(var i=0;i < lsr.length; i ++){
		lsr[i].handleEvent(null);
	}
}

nsHttpPolling.prototype.open = function (mode)
{
	if(!this.connection){
		this.connection=Components.classes[kXMLHTTP_CID].getService(nsIXmlHttpRequest);
//		this.connection.addEventListener("load", this, true);
	}
	this.connection.onload = this;
	this.connection.onerror = this;
    this.connection.open("POST", this.url, mode, this.user, this.pass);
    this.connection.setRequestHeader('Content-Type','"application/x-www-form-urlencoded');
}

nsHttpPolling.prototype.sendRequest = function (str)
{
    var req = this.sid;
	if (this.sid=="0")
		str="<stream:stream to='"
					+this.server 
					+"' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"+str;
    if (_HAVE_KEYS)
    {
		req+=";"+this.keys.getKey();
		if (this.keys.lastKey()){
			this.keys = new JSJaCKeys();
			req += ';'+this.keys.getKey();
		}
		req += ',' + str;
    }
	this.open(false);
	this.connection.send(req);
}

nsHttpPolling.prototype.send = function (request)
{
	dumpString("Sent: " + request);
	this.sendRequest(request);	
}

nsHttpPolling.prototype.initKeys = function ()
{
    this.sid="0";
    this.keys = new JSJaCKeys();
}

nsHttpPolling.prototype.QueryInterface = function(iid) {
  if (!iid.equals(Ci.nsIHttpPolling) &&
	  !iid.equals(Ci.nsIAlertListener) &&
	  !iid.equals(Ci.nsIDOMEventListener) &&
      !iid.equals(nsISupports))
    throw Components.results.NS_ERROR_NO_INTERFACE;
  return this;
}
/**
 * JS XPCOM component registration goop:
 *
 * We set ourselves up to observe the xpcom-startup category.  This provides
 * us with a starting point.
 */

var nsHttpPollingModule = new Object();

nsHttpPollingModule.registerSelf =
function (compMgr, fileSpec, location, type)
{
  dump ("\n Registering  nsHttpPolling ... ");
  compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  compMgr.registerFactoryLocation(kHTTP_POLLING_UID,
                                  "nsHttpPolling",
                                  kHTTP_POLLING_CID,
                                  fileSpec, 
                                  location, 
                                  type);
  dump (" ok \n ");
}

nsHttpPollingModule.getClassObject =
function (compMgr, cid, iid)
{
  if (!cid.equals(kHTTP_POLLING_UID))
    throw Components.results.NS_ERROR_NO_INTERFACE;

  if (!iid.equals(Ci.nsIFactory))
    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;

  return nsHttpPollingFactory;
}

nsHttpPollingModule.canUnload =
function (compMgr)
{
	if (this.updateTimer)
		this.updateTimer.cancel();
	this.updateTimer = null;
  return true;
}

var nsHttpPollingFactory = new Object();

nsHttpPollingFactory.createInstance = function (outer, iid)
{
  if (outer != null)
    throw Components.results.NS_ERROR_NO_AGGREGATION;

  if (!iid.equals(Ci.nsIHttpPolling) &&
      !iid.equals(nsISupports))
    throw Components.results.NS_ERROR_NO_INTERFACE;

  return new nsHttpPolling();
}

function NSGetModule(compMgr, fileSpec) {
  return nsHttpPollingModule;
}

/*
  Helper Functions
*/

function dumpString(aMsg){
if(_DEBUG)
    dump(aMsg  + "\n");
}


/* ***
 * set of sha1 hash keys for securing sessions
 */											
function JSJaCKeys() {
	var seed = Math.random();
	var sha1 = Cc["@mozilla.org/sha1service;1"].getService (Ci.nsISHA1Service);
	this.k = new Array();
	this.k[0] = seed.toString();
	
	for (var i=1; i<_NKEYS; i++) {
		this.k[i] = sha1.b64_sha1(this.k[i-1]);
//		dumpString(i + ": " + this.k[i]);
	}

	this.indexAt = _NKEYS-1;
	this.getKey = function() { 
		return this.k[this.indexAt--]; 
	};
	this.lastKey = function() { return (this.indexAt == 0); };
	this.size = function() { return this.k.length; };
}
